package com.jxpanda.infrastructure.crypto.algorithm.encrypt.contract;

import com.jxpanda.infrastructure.crypto.constant.AlgorithmEncrypt;
import lombok.AccessLevel;
import lombok.Getter;

import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Objects;

@Getter
public class AbstractAsymmetricEncrypt implements AsymmetricEncrypt {

    private final String algorithm;

    private final KeyPair key;

    private final PublicKey publicKey;

    private final PrivateKey privateKey;

    public AbstractAsymmetricEncrypt(String algorithm, KeyPair keyPair) {
        this.algorithm = algorithm;
        this.key = keyPair;
        this.publicKey = keyPair.getPublic();
        this.privateKey = keyPair.getPrivate();
    }

    public AbstractAsymmetricEncrypt(String algorithm, PublicKey publicKey) {
        this.algorithm = algorithm;
        this.publicKey = publicKey;
        this.key = null;
        this.privateKey = null;
    }

    public AbstractAsymmetricEncrypt(String algorithm, PrivateKey privateKey) {
        this.algorithm = algorithm;
        this.privateKey = privateKey;
        this.publicKey = null;
        this.key = null;
    }

    public AbstractAsymmetricEncrypt(String algorithm, PublicKey publicKey, PrivateKey privateKey) {
        this.algorithm = algorithm;
        this.key = new KeyPair(publicKey, privateKey);
        this.publicKey = publicKey;
        this.privateKey = privateKey;
    }


    @SuppressWarnings("unchecked")
    @Getter(AccessLevel.PROTECTED)
    public static abstract class Builder<S extends AlgorithmEncrypt.Asymmetric, E extends AbstractAsymmetricEncrypt> {
        private final S asymmetricAlgorithm;
        private KeyPair keyPair;
        private PublicKey publicKey;
        private PrivateKey privateKey;

        protected Builder(S asymmetricAlgorithm) {
            this.asymmetricAlgorithm = asymmetricAlgorithm;
        }

        public Builder<S, E> keyPair(KeyPair keyPair) {
            this.keyPair = keyPair;
            return this;
        }

        public Builder<S, E> publicKey(PublicKey publicKey) {
            this.publicKey = publicKey;
            return this;
        }

        public Builder<S, E> privateKey(PrivateKey privateKey) {
            this.privateKey = privateKey;
            return this;
        }

        protected KeyPair getKeyPairOrDefault() {
            return Objects.requireNonNullElse(keyPair, asymmetricAlgorithm.keyPairProvider().generateKey());
        }

        public E build() {
            String algorithm = asymmetricAlgorithm.getAlgorithm();
            if (getKeyPair() != null) {
                return (E) new AbstractAsymmetricEncrypt(algorithm, getKeyPair());
            }
            if (getPublicKey() != null && getPrivateKey() != null) {
                return (E) new AbstractAsymmetricEncrypt(algorithm, getPublicKey(), getPrivateKey());
            }
            if (getPublicKey() != null) {
                return (E) new AbstractAsymmetricEncrypt(algorithm, getPublicKey());
            }
            if (getPrivateKey() != null) {
                return (E) new AbstractAsymmetricEncrypt(algorithm, getPrivateKey());
            }
            throw new IllegalArgumentException("The KeyPair, PublicKey and PrivateKey can not be null at the same time");
        }

        public E buildDefault() {
            String algorithm = asymmetricAlgorithm.getAlgorithm();
            return (E) new AbstractAsymmetricEncrypt(algorithm, getKeyPairOrDefault());
        }
    }

}
